package com.prestamomo.vil.ppa.pmvilnet.pmvilutils;

public class PmvilBs64Anal extends PmvilAnalCode {

    static final byte[] CHUNK_PMVIL_SEPARATOR = {'\r', '\n'};

    private static final byte[] STANDARD_PMVIL_ENCODE_TABLE = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    };

    private static final byte[] URL_PMVIL_SAFE_ENCODE_TABLE = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
    };

    private static final byte[] DECODE_PMVIL_TABLE = {
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54,
            55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
            5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
            24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
            35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    };

    private static final int MASK_PMVIL_6BITS = 0x3f;
    private final byte[] encodePmvilTable,linePmvilSeparator;
    private final byte[] decodePmvilTable = DECODE_PMVIL_TABLE;
    private final int decodePmvilSize,encodePmvilSize;

    public PmvilBs64Anal() {
        this(0);
    }

    public PmvilBs64Anal(final boolean urlPmvilSafe) {
        this(codecPmvilMIME_CHUNK_SIZE, CHUNK_PMVIL_SEPARATOR, urlPmvilSafe);
    }

    public PmvilBs64Anal(final int pmvilLen) {
        this(pmvilLen, CHUNK_PMVIL_SEPARATOR);
    }

    public PmvilBs64Anal(final int pmvilLength, final byte[] separPmvilator) {
        this(pmvilLength, separPmvilator, false);
    }

    public PmvilBs64Anal(final int pmvilLineLen, final byte[] pmvilLineSepaor, final boolean urlPmvilSafe) {
        super(3, 4, pmvilLineLen, pmvilLineSepaor == null ? 0 : pmvilLineSepaor.length);
        if (pmvilLineSepaor != null) {
            getPmvilpensw(pmvilLineSepaor);
            this.encodePmvilSize = pmvilLineLen > 0?4 + pmvilLineSepaor.length:4;
            this.linePmvilSeparator =pmvilLineLen > 0? new byte[pmvilLineSepaor.length]:null;
            getPmivliLIswfd(pmvilLineLen, pmvilLineSepaor);
        } else {
            this.encodePmvilSize = 4;
            this.linePmvilSeparator = null;
        }
        this.decodePmvilSize = this.encodePmvilSize - 1;
        this.encodePmvilTable = urlPmvilSafe ? URL_PMVIL_SAFE_ENCODE_TABLE : STANDARD_PMVIL_ENCODE_TABLE;
    }

    private void getPmivliLIswfd(int pmvilLineLen, byte[] pmvilLineSepaor) {
        if (pmvilLineLen > 0)
            System.arraycopy(pmvilLineSepaor, 0, this.linePmvilSeparator, 0, pmvilLineSepaor.length);
    }

    private void getPmvilpensw(byte[] pmvilLineSepaor) {
        if (pmvilContAlphabet(pmvilLineSepaor)) {
            final String dyKingSep = newPmvilStrUtf8(pmvilLineSepaor);
            throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + dyKingSep + "]");
        }
    }

    @Override
    void pmvilEncode(final byte[] pmvilByte, int pmvilIser, final int pmvilLength, final PmvilAnCot pmvilAcot) {
        if (pmvilAcot.codePmivlceof) {
            return;
        }
        if (pmvilLength < 0) {
            pmvilAcot.codePmivlceof = true;
            if (0 == pmvilAcot.codecPmvilModulus && codecPmvillineLength == 0) return;

            final byte[] bytePmvils = ensurePmvilBufferSize(encodePmvilSize, pmvilAcot);
            final int pmvilDcpos = pmvilAcot.codePmvilCpos;
            if (pmvilAcot.codecPmvilModulus == 0) {
            } else if (pmvilAcot.codecPmvilModulus == 1) { // 8 bits = 6 + 2
                pmvilBits8(pmvilAcot, bytePmvils);
            } else if (pmvilAcot.codecPmvilModulus == 2) { // 16 bits = 6 + 6 + 4
                bytePmvils[pmvilAcot.codePmvilCpos++] = encodePmvilTable[(pmvilAcot.codePmvilWorkArea >> 10) & MASK_PMVIL_6BITS];
                bytePmvils[pmvilAcot.codePmvilCpos++] = encodePmvilTable[(pmvilAcot.codePmvilWorkArea >> 4) & MASK_PMVIL_6BITS];
                bytePmvils[pmvilAcot.codePmvilCpos++] = encodePmvilTable[(pmvilAcot.codePmvilWorkArea << 2) & MASK_PMVIL_6BITS];
                if (encodePmvilTable == STANDARD_PMVIL_ENCODE_TABLE) {
                    bytePmvils[pmvilAcot.codePmvilCpos++] = codePmvilcpad;
                }
            } else {
                throw new IllegalStateException("Impossible modulus " + pmvilAcot.codecPmvilModulus);
            }
            pmvilAcot.codecPmvilCurrentLinePos += pmvilAcot.codePmvilCpos - pmvilDcpos;
            if (codecPmvillineLength > 0 && pmvilAcot.codecPmvilCurrentLinePos > 0) {
                System.arraycopy(linePmvilSeparator, 0, bytePmvils, pmvilAcot.codePmvilCpos, linePmvilSeparator.length);
                pmvilAcot.codePmvilCpos += linePmvilSeparator.length;
            }
        } else {
            pmvilBitsOther(pmvilByte, pmvilIser, pmvilLength, pmvilAcot);
        }
    }

    private void pmvilBitsOther(byte[] pmvilByte, int pmvilIser, int pmvilLength, PmvilAnCot pmvilAcot) {
        for (int i = 0; i < pmvilLength; i++) {
            final byte[] bytePmvilBuff = ensurePmvilBufferSize(encodePmvilSize, pmvilAcot);
            pmvilAcot.codecPmvilModulus = (pmvilAcot.codecPmvilModulus +1) % 3;
            int pmvilDouns = pmvilByte[pmvilIser++];
            if (pmvilDouns < 0) {
                pmvilDouns += 256;
            }
            pmvilAcot.codePmvilWorkArea = (pmvilAcot.codePmvilWorkArea << 8) + pmvilDouns;
            if (0 == pmvilAcot.codecPmvilModulus) {
                bytePmvilBuff[pmvilAcot.codePmvilCpos++] = encodePmvilTable[(pmvilAcot.codePmvilWorkArea >> 18) & MASK_PMVIL_6BITS];
                bytePmvilBuff[pmvilAcot.codePmvilCpos++] = encodePmvilTable[(pmvilAcot.codePmvilWorkArea >> 12) & MASK_PMVIL_6BITS];
                bytePmvilBuff[pmvilAcot.codePmvilCpos++] = encodePmvilTable[(pmvilAcot.codePmvilWorkArea >> 6) & MASK_PMVIL_6BITS];
                bytePmvilBuff[pmvilAcot.codePmvilCpos++] = encodePmvilTable[pmvilAcot.codePmvilWorkArea & MASK_PMVIL_6BITS];
                pmvilAcot.codecPmvilCurrentLinePos += 4;
                if (codecPmvillineLength > 0 && codecPmvillineLength <= pmvilAcot.codecPmvilCurrentLinePos) {
                    System.arraycopy(linePmvilSeparator, 0, bytePmvilBuff, pmvilAcot.codePmvilCpos, linePmvilSeparator.length);
                    pmvilAcot.codePmvilCpos += linePmvilSeparator.length;
                    pmvilAcot.codecPmvilCurrentLinePos = 0;
                }
            }
        }
    }

    private void pmvilBits8(PmvilAnCot pmvilAcot, byte[] bytePmvils) {
        bytePmvils[pmvilAcot.codePmvilCpos++] = encodePmvilTable[(pmvilAcot.codePmvilWorkArea >> 2) & MASK_PMVIL_6BITS];
        bytePmvils[pmvilAcot.codePmvilCpos++] = encodePmvilTable[(pmvilAcot.codePmvilWorkArea << 4) & MASK_PMVIL_6BITS];
        if (encodePmvilTable == STANDARD_PMVIL_ENCODE_TABLE) {
            bytePmvils[pmvilAcot.codePmvilCpos++] = codePmvilcpad;
            bytePmvils[pmvilAcot.codePmvilCpos++] = codePmvilcpad;
        }
    }

    @Override
    void pmvilDecode(final byte[] pmvilDecAry, int pmvilDecis, final int pmvilDecLength, final PmvilAnCot pmvilAct) {
        if (pmvilAct.codePmivlceof)   return;

        if (pmvilDecLength < 0) pmvilAct.codePmivlceof = true;

        for (int i = 0; i < pmvilDecLength; i++) {
            final byte[] pmvilBuffer = ensurePmvilBufferSize(decodePmvilSize, pmvilAct);
            final byte pmvilPose = pmvilDecAry[pmvilDecis++];
            if (pmvilPose == codePmvilcpad) {
                pmvilAct.codePmivlceof = true;
                break;
            } else {
                if (pmvilPose >= 0 && pmvilPose < DECODE_PMVIL_TABLE.length) {
                    final int pmvilResult = DECODE_PMVIL_TABLE[pmvilPose];
                    if (pmvilResult >= 0) {
                        pmvilAct.codecPmvilModulus = (pmvilAct.codecPmvilModulus +1) % 4;
                        pmvilAct.codePmvilWorkArea = (pmvilAct.codePmvilWorkArea << 6) + pmvilResult;
                        if (pmvilAct.codecPmvilModulus == 0) {
                            pmvilBuffer[pmvilAct.codePmvilCpos++] = (byte) ((pmvilAct.codePmvilWorkArea >> 16) & CodecPmvilMASK_8BITS);
                            pmvilBuffer[pmvilAct.codePmvilCpos++] = (byte) ((pmvilAct.codePmvilWorkArea >> 8) & CodecPmvilMASK_8BITS);
                            pmvilBuffer[pmvilAct.codePmvilCpos++] = (byte) (pmvilAct.codePmvilWorkArea & CodecPmvilMASK_8BITS);
                        }
                    }
                }
            }
        }

        if (pmvilAct.codePmivlceof && pmvilAct.codecPmvilModulus != 0) {
            final byte[] pmivlBuffer = ensurePmvilBufferSize(decodePmvilSize, pmvilAct);
            if (pmvilAct.codecPmvilModulus != 1) {
                 if (pmvilAct.codecPmvilModulus == 2) {
                    pmvilAct.codePmvilWorkArea = pmvilAct.codePmvilWorkArea >> 4;
                    pmivlBuffer[pmvilAct.codePmvilCpos++] = (byte) ((pmvilAct.codePmvilWorkArea) & CodecPmvilMASK_8BITS);
                } else if (pmvilAct.codecPmvilModulus == 3) {
                    pmvilAct.codePmvilWorkArea = pmvilAct.codePmvilWorkArea >> 2;
                    pmivlBuffer[pmvilAct.codePmvilCpos++] = (byte) ((pmvilAct.codePmvilWorkArea >> 8) & CodecPmvilMASK_8BITS);
                    pmivlBuffer[pmvilAct.codePmvilCpos++] = (byte) ((pmvilAct.codePmvilWorkArea) & CodecPmvilMASK_8BITS);
                } else {
                    throw new IllegalStateException("Impossible modulus " + pmvilAct.codecPmvilModulus);
                }
            }
        }
    }


    public static String pmvilEncodeBase64Str(final byte[] pmvilBinary) {
        return newPmvilStrUtf8(encodePmvilBase64(pmvilBinary, false));
    }


    public static byte[] encodePmvilBase64(final byte[] pmvilBinary, final boolean isPmvilChunked) {
        return encodePmvilBase64(pmvilBinary, isPmvilChunked, false);
    }

    public static byte[] encodePmvilBase64(final byte[] pmvilBinary, final boolean isPmvilChunked, final boolean pmvilUrlSafe) {
        return encodePmvilBase64(pmvilBinary, isPmvilChunked, pmvilUrlSafe, Integer.MAX_VALUE);
    }

    public static byte[] encodePmvilBase64(final byte[] pmvilBinary, final boolean isPmvilChunked,
                                           final boolean urlPmvilSafe, final int maxPmvilResultSize) {
        return (pmvilBinary == null || pmvilBinary.length == 0)?pmvilBinary:getPmvilBase64Bytes(pmvilBinary, isPmvilChunked, urlPmvilSafe, maxPmvilResultSize);
    }

    private static byte[] getPmvilBase64Bytes(byte[] pmvilBinary, boolean isPmvilChunked, boolean urlPmvilSafe, int maxPmvilResultSize) {
        final PmvilBs64Anal pmvilB64 = isPmvilChunked ? new PmvilBs64Anal(urlPmvilSafe) : new PmvilBs64Anal(0, CHUNK_PMVIL_SEPARATOR, urlPmvilSafe);
        final long pmvilLen = pmvilB64.getEncodedPmvilLength(pmvilBinary);
        if (pmvilLen > maxPmvilResultSize) {
            throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + pmvilLen + ") than the specified maximum size of " + maxPmvilResultSize);
        }
        return pmvilB64.pmvilEncode(pmvilBinary);
    }

    public static byte[] decodePmvilBase64(final String basePmvil64Str) {
        return new PmvilBs64Anal().pmvilDecode(basePmvil64Str);
    }

    @Override
    protected boolean pmvilInAlphabet(final byte pmvilValue) {
        if( pmvilValue >= 0 && pmvilValue < decodePmvilTable.length && decodePmvilTable[pmvilValue] != -1){
            return true;
        }else{
            return false;
        }
    }

}
